package org.jflame.commons.util;

import org.jflame.commons.model.Chars;

import java.time.LocalDate;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.math.NumberUtils;

/**
 * 中国身份证表示类.
 * <ul>
 * <li>身份号码组成规则：户籍地编码(6位)+出生日期(8位)+顺序码(3位)+校验码(1位). 新版身份证共18位,旧版只有15位(日期年份按2位表示,且没有检验位)</li>
 * <li>顺序码: 表示在同一地址码所标识的区域范围内，对同年、同月、同日出生的人编定的顺序号，顺序码的奇数分配给男性，偶数分配给女性.</li>
 * <li>校验码的计算方法： 将前面17位数分别乘以不同的系数,再把乘积相加,然后除以11取余数。<br>
 * 从第1位到第17位的系数分别为：7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2.<br>
 * 余数:0 1 2 3 4 5 6 7 8 9 10分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3.
 * </ul>
 * 使用示例:
 * 
 * <pre>
 * 
 * ChineseIDCard idCard = ChineseIDCard.parse("11010119900101123X");// 解析身份证各部分
 * System.out.println(idCard.gender());// 获取性别
 * 
 * ChineseIDCard.isValid("11010119900101123X");// 验证身份证号码是否有效
 * </pre>
 * 
 * @author charles.zhang
 */
public record ChineseIDCard(String province, String city, String county, LocalDate birthday, int seqNo,
        String checkCode) {

    private static int[] power = { 7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2 };

    /**
     * 返回性别,0=女,1=男
     * 
     * @return
     */
    public int gender() {
        return seqNo % 2;
    }

    /**
     * 省编码
     * 
     * @return
     */
    public String provinceCode() {
        return province + "0000";
    }

    /**
     * 市编码
     * 
     * @return
     */
    public String cityCode() {
        return province + city + "00";
    }

    /**
     * 县/区编码简码
     * 
     * @return
     */
    public String countyCode() {
        return province + city + county;
    }

    /**
     * 验证是否是正确的身份证号码
     *
     * @param idcard 身份证号码
     * @return 正确的身份证号码返回true
     */
    public static boolean isValid(String idcard) {
        if (idcard == null) {
            return false;
        }
        if (idcard.length() == 15) {
            try {
                idcard = to2nd(idcard);
            } catch (RuntimeException e) {
                return false;
            }
        }
        return isValidate18Idcard(idcard);
    }

    /**
     * 身份证一代转二代<br>
     * 第一代身份证为15位的年份是2位数表示(即yy)和少了检验位, 二代身份证18位
     * 
     * @param idcard15 15位第一代身份证
     * @return
     */
    public static String to2nd(String idcard15) {
        String idcard18 = null;
        if (!NumberUtils.isDigits(idcard15) || idcard15.length() != 15) {
            throw new IllegalArgumentException(idcard15 + "不是有效的一代身份证");
        }
        // 获取出生年月日
        String birthday = idcard15.substring(6, 12);
        LocalDate birthdate = DateHelper.parseLocalDate(birthday, "yyMMdd");
        idcard18 = idcard15.substring(0, 6) + birthdate.getYear() + idcard15.substring(8);

        char[] chars = idcard18.toCharArray();

        int[] bits = converCharToInt(chars);
        int mod = calModOfSum(bits);
        // 获取和值与11取模得到余数进行校验码
        char checkCode = getCheckCodeBySum(mod);
        // 获取不到校验位
        if (Chars.NULL == checkCode) {
            return null;
        }

        // 将前17位与第18位校验码拼接
        idcard18 += checkCode;
        return idcard18;

    }

    /**
     * 将身份证的每位和对应位的加权因子相乘之后，再得到和值
     *
     * @param bit bit
     * @param power power
     * @return
     */
    private static int calModOfSum(int[] bit) {
        int sum = 0;
        if (power.length != bit.length) {
            return sum;
        }
        for (int i = 0; i < bit.length; i++) {
            for (int j = 0; j < power.length; j++) {
                if (i == j) {
                    sum = sum + bit[i] * power[j];
                }
            }
        }
        return sum % 11;
    }

    /**
     * 将和值与11取模得到余数进行校验码判断
     *
     * @param checkCode
     * @param sum17
     * @return 校验位
     */
    private static char getCheckCodeBySum(int mod) {

        return switch (mod) {
            case 10 -> '2';
            case 9 -> '3';
            case 8 -> '4';
            case 7 -> '5';
            case 6 -> '6';
            case 5 -> '7';
            case 4 -> '8';
            case 3 -> '9';
            case 2 -> 'X';
            case 1 -> '0';
            case 0 -> '1';
            default -> Chars.NULL;
        };
        /* char checkCode;switch (sum17 % 11) {
            case 10:
                checkCode = '2';
                break;
            case 9:
                checkCode = '3';
                break;
            case 8:
                checkCode = '4';
                break;
            case 7:
                checkCode = '5';
                break;
            case 6:
                checkCode = '6';
                break;
            case 5:
                checkCode = '7';
                break;
            case 4:
                checkCode = '8';
                break;
            case 3:
                checkCode = '9';
                break;
            case 2:
                checkCode = 'X';
                break;
            case 1:
                checkCode = '0';
                break;
            case 0:
                checkCode = '1';
                break;
            default:
                checkCode = Chars.NULL;
                break;
        }
        return checkCode;*/
    }

    /*
     * 省，直辖市简码表(代码前两位,如北京:11 -> 110000)： 
     * 11:"北京",12:"天津",13:"河北",14:"山西",15:"内蒙古",21:"辽宁",22:"吉林",23:"黑龙江",31:"上海",32:"江苏", 33:"浙江",34:"安徽",
     * 35:"福建",36:"江西",37:"山东",41:"河南",42:"湖北",43:"湖南",44:"广东",45:"广西",46:"海南",50:"重庆", 51:"四川",52:"贵州",
     * 53:"云南",54:"西藏",61:"陕西",62:"甘肃",63:"青海",64:"宁夏",65:"新疆",71:"台湾",81:"香港",82:"澳门",91:"国外"
     */
    static String[] cityCode = { "11","12","13","14","15","21","22","23","31","32","33","34","35","36","37","41","42",
            "43","44","45","46","50","51","52","53","54","61","62","63","64","65","71","81","82","91" };

    private static boolean isValidate18Idcard(String idcard) {
        if (idcard.length() != 18) {
            return false;

        }
        // 区域码检验
        if (!ArrayUtils.contains(cityCode, idcard.substring(0, 2))) {
            return false;
        }
        char[] chars = idcard.toCharArray();
        // 校验位检验
        char idcard18Code = chars[17];
        if (!NumberHelper.isLatinDigits(idcard18Code)) {
            idcard18Code = Character.toUpperCase(idcard18Code);
            if (idcard18Code != 'X') {
                return false;
            }
        }
        chars = ArrayUtils.remove(chars, 17);
        int[] bits;
        try {
            bits = converCharToInt(chars);
        } catch (NumberFormatException e) {
            return false;
        }

        int mod = calModOfSum(bits); // 获取和值与11取模得到余数进行校验码判断
        char checkCode = getCheckCodeBySum(mod);
        if (checkCode == Chars.NULL) {
            return false;
        }

        return idcard18Code == checkCode;
    }

    private static int[] converCharToInt(char[] c) throws NumberFormatException {
        int[] nums = new int[c.length];
        final int radix = 10;
        for (int i = 0; i < c.length; i++) {
            if (!NumberHelper.isLatinDigits(c[i])) {
                throw new NumberFormatException("身份证号码含非数字字符");
            }
            nums[i] = Character.digit(c[i], radix);
        }
        return nums;
    }

    /**
     * 从身份证号字符串解析出各组成部分
     * 
     * @param idcard 身份证号字符串
     * @return ChineseIDCard
     */
    public static ChineseIDCard parse(String idcard) {
        if (!ChineseIDCard.isValid(idcard)) {
            throw new IllegalArgumentException(idcard + " is not a Chinese ID number");
        }
        LocalDate birthday;
        int seqNo;
        if (idcard.length() == 15) {
            birthday = DateHelper.parseLocalDate(idcard.substring(6, 12), "yyMMdd");
            seqNo = Integer.parseInt(idcard.substring(12, 15));

        } else {
            birthday = DateHelper.parseLocalDate(idcard.substring(6, 14), "yyyyMMdd");
            seqNo = Integer.parseInt(idcard.substring(14, 17));
        }
        return new ChineseIDCard(idcard.substring(0, 2), idcard.substring(2, 4), idcard.substring(4, 6), birthday,
                seqNo, idcard.substring(17, 18));
    }

    @Override
    public String toString() {
        return province + city + county + DateHelper.format(birthday, DateHelper.yyyyMMdd) + seqNo + checkCode;
    }

}
